package de.jmda.fx.node;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;

import org.apache.log4j.Logger;

import de.jmda.fx.node.behaviour.BehaviourHighlighting;
import de.jmda.fx.node.behaviour.BehaviourHighlightingOnMouseEntered;
import de.jmda.fx.node.behaviour.BehaviourHighlightingOnMouseExited;
import de.jmda.fx.node.behaviour.BehaviourMouseDragging;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingEvents;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingStart;
import de.jmda.fx.node.behaviour.BehaviourMouseDraggingStop;
import de.jmda.fx.node.behaviour.Draggable;
import de.jmda.fx.node.behaviour.Highlightable;
import de.jmda.util.gui.awt.graphics.RelationEndpoint.Style;

public class ArrowOrthogonal2
		extends Polygon
		implements Highlightable, Draggable
{
	private final static Logger LOGGER = Logger.getLogger(ArrowOrthogonal2.class);

	/**
	 * head of arrow, often touches (anchor point of) another shape
	 * <p>
	 * Will be updated if arrow moves.
	 */
	private ObjectProperty<Point2D> head = new SimpleObjectProperty<Point2D>(new Point2D(0,0));
	public Point2D getHead() { return head.get(); }
	protected void setHead(Point2D head) { this.head.set(head); }
	public ObjectProperty<Point2D> headProperty() { return head; }

	/**
	 * tail of arrow, often works as anchor point for other shapes
	 * <p>
	 * Will be updated if arrow moves.
	 */
	private ObjectProperty<Point2D> tail = new SimpleObjectProperty<Point2D>(new Point2D(0,0));	
	public Point2D getTail() { return tail.get(); }
	protected void setTail(Point2D tail) { this.tail.set(tail); }
	public ObjectProperty<Point2D> tailProperty() { return tail; }

	private double length;
	private Style style;
	private boolean dashed;
	private double headSize;
	private Orientation orientation;

	/**
	 * injected into constructor, used to recalculate {@link #head} after moves
	 * of arrow
	 */
	private Point2D initialHeadPosition;

	/**
	 * initialized in {@link #init()}, used to recalculate {@link #tail} after
	 * moves of arrow
	 */
	private Point2D initialTailPosition;

	public ArrowOrthogonal2(
			Point2D head,
			double length,
			Style style,
			boolean dashed,
			double headSize,
			Orientation orientation)
	{
		this(head, length, style, dashed, headSize, orientation, "");
	}

	public ArrowOrthogonal2(
			Point2D head,
			double length,
			Style style,
			boolean dashed,
			double headSize,
			Orientation orientation,
			String id)
	{
		super();
		
		setId(id);

		initialHeadPosition = head;

		this.head.set(head);

		this.length = length;
		this.style = style;
		this.dashed = dashed;
		this.headSize = headSize;
		this.orientation = orientation;

		init();
		
		boundsInParentProperty().addListener
		(
				new ChangeListener<Bounds>()
				{
					@Override
          public void changed(
          		ObservableValue<? extends Bounds> observable,
              Bounds oldValue, Bounds newValue)
          {
						headProperty().set(
//						setHead(
								new Point2D(
										getLayoutX() + initialHeadPosition.getX(),
										getLayoutY() + initialHeadPosition.getY()));
						tailProperty().set(
//						setTail(
								new Point2D(
										getLayoutX() + initialTailPosition.getX(),
										getLayoutY() + initialTailPosition.getY()));
						if (ArrowOrthogonal2.this.getId().equals("the one"))
						{
							LOGGER.debug(
//									coordinatesAsString(ArrowOrthogonal.this) + " " +
									boundsAsString(getBoundsInParent()));
						}
          }
				}
		);
	}

	public Orientation getOrientation()
  {
  	return orientation;
  }
	
	public void setLength(double length)
	{
		this.length = length;
		System.out.println(
				  "before init: "
				+ boundsAsString(getBoundsInParent()) + " "
				+ coordinatesAsString(this));
//		Point2D initialHeadPositionOld = initialHeadPosition;
//		initialHeadPosition = new Point2D(0,0);
//		head.set(new Point2D(0,0));
//		setLayoutX(head.get().getX());
//		setLayoutY(head.get().getY());
		init();
//		initialHeadPosition = initialHeadPositionOld;
//		setLayoutX(getLayoutX());
		System.out.println(
				  "after  init: "
				+ boundsAsString(getBoundsInParent()) + " "
				+ coordinatesAsString(this));
	}

	public double getLength()
  {
	  return length;
  }

	public void activateDragging()
	{
		BehaviourMouseDraggingEvents behaviourMouseDraggingEvents =
				new BehaviourMouseDraggingEvents();

		new BehaviourMouseDraggingStart(this, behaviourMouseDraggingEvents);
		new BehaviourMouseDraggingStop( this, behaviourMouseDraggingEvents);
		new BehaviourMouseDragging(     this, behaviourMouseDraggingEvents);
	}

	@Override
  public void deactivateDragging()
  {
		LOGGER.warn("not (yet) implemented");
  }

	public void activateHighlighting()
	{
		new BehaviourHighlightingOnMouseEntered<MouseEvent>(this, this);
		new BehaviourHighlightingOnMouseExited<MouseEvent>(this, this);
	}

	@Override
  public void deactivateHighlighting()
  {
		LOGGER.warn("not (yet) implemented");
  }

	@Override
  public void highlightingOn()
  {
		setStrokeWidth(getStrokeWidth() + BehaviourHighlighting.STROKE_WIDTH_DIFF);
  }

	@Override
  public void highlightingOff()
  {
		setStrokeWidth(getStrokeWidth() - BehaviourHighlighting.STROKE_WIDTH_DIFF);
  }

	/**
	 * Computes all coordinates of the polygon, sets {@link #tailProperty()} and
	 * registers listeners on {@link #headProperty()} and {@link #tailProperty()}.
	 */
	private void init()
  {
		// shortcut
		ObservableList<Double> points = getPoints();

		// reset polygon
		points.clear();

		setStroke(Color.BLACK);
//		setStrokeWidth(1);
//		getStyleClass().add("typeshape");

		if (dashed)
		{
			setStrokeDashOffset(5);
		}

		double x = head.get().getX();
		double y = head.get().getY();

		if (orientation == Orientation.DOWN)
		{
			if (style == Style.FILLED)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y - headSize,
						x + headSize, y - headSize,
						x           , y           ,
						x           , y - length
				);
			}
			else if (style == Style.TRANSPARENT)
			{
				points.setAll
				(
						x           , y - headSize,
						x - headSize, y - headSize,
						x           , y           ,
						x + headSize, y - headSize,
						x           , y - headSize,
						x           , y - length
				);
			}
			else if (style == Style.OPEN)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y - headSize,
						x           , y           ,
						x + headSize, y - headSize,
						x           , y           ,
						x           , y - length
				);
			}
			else if (style == Style.NONE)
			{
				points.setAll
				(
						x, y,
						x, y - length
				);
			}
//			setTail(new Point2D(x, y - length));
			tail.set(new Point2D(x, y - length));
		}
		else if (orientation == Orientation.UP)
		{
			if (style == Style.FILLED)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y + headSize,
						x + headSize, y + headSize,
						x           , y           ,
						x           , y + length
				);
			}
			else if (style == Style.TRANSPARENT)
			{
				points.setAll
				(
						x           , y + headSize,
						x - headSize, y + headSize,
						x           , y           ,
						x + headSize, y + headSize,
						x           , y + headSize,
						x           , y + length
				);
			}
			else if (style == Style.OPEN)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y + headSize,
						x           , y           ,
						x + headSize, y + headSize,
						x           , y           ,
						x           , y + length
				);
			}
			else if (style == Style.NONE)
			{
				points.setAll
				(
						x, y,
						x, y + length
				);
			}
//			setTail(new Point2D(x, y + length));
			tail.set(new Point2D(x, y + length));
		}
		else if (orientation == Orientation.LEFT)
		{
			if (style == Style.FILLED)
			{
				points.setAll
				(
						x           , y           ,
						x + headSize, y - headSize,
						x + headSize, y + headSize,
						x           , y           ,
						x + length  , y
				);
			}
			else if (style == Style.TRANSPARENT)
			{
				points.setAll
				(
						x + headSize, y           ,
						x + headSize, y - headSize,
						x           , y           ,
						x + headSize, y + headSize,
						x + headSize, y           ,
						x + length  , y
				);
			}
			else if (style == Style.OPEN)
			{
				points.setAll
				(
						x           , y           ,
						x + headSize, y - headSize,
						x           , y           ,
						x + headSize, y + headSize,
						x           , y           ,
						x + length  , y
				);
			}
			else if (style == Style.NONE)
			{
				points.setAll
				(
						x         , y,
						x + length, y
				);
			}
//			setTail(new Point2D(x + length, y));
			tail.set(new Point2D(x + length, y));
		}
		else if (orientation == Orientation.RIGHT)
		{
			if (style == Style.FILLED)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y - headSize,
						x - headSize, y + headSize,
						x           , y           ,
						x - length  , y
				);
			}
			else if (style == Style.TRANSPARENT)
			{
				points.setAll
				(
						x - headSize, y           ,
						x - headSize, y - headSize,
						x           , y           ,
						x - headSize, y + headSize,
						x - headSize, y           ,
						x - length  , y
				);
			}
			else if (style == Style.OPEN)
			{
				points.setAll
				(
						x           , y           ,
						x - headSize, y - headSize,
						x           , y           ,
						x - headSize, y + headSize,
						x           , y           ,
						x - length  , y
				);
			}
			else if (style == Style.NONE)
			{
				points.setAll
				(
						x         , y,
						x - length, y
				);
			}
//			setTail(new Point2D(x - length, y));
			tail.set(new Point2D(x - length, y));
		}
		
		initialTailPosition = tail.get();

		if (style == Style.FILLED)
		{
			setFill(Color.BLACK);
		}
		if (style == Style.TRANSPARENT)
		{
			setFill(Color.TRANSPARENT);
		}
	}

	public static String coordinatesAsString(ArrowOrthogonal2 arrow)
	{
		return
				String.format
				(
						"CRDS " +
						"layo x|y [%5.1f|%5.1f] " +
						"head x|y [%5.1f|%5.1f] " +
						"tail x|y [%5.1f|%5.1f]",
						arrow.getLayoutX()     , arrow.getLayoutY(),
						arrow.head.get().getX(), arrow.head.get().getY(),
						arrow.tail.get().getX(), arrow.tail.get().getY()
				);
	}
	
	public static String boundsAsString(Bounds bounds)
	{
		return
				String.format
				(
						"BNDS " +
						"min x|y [%5.1f|%5.1f] " +
						"max x|y [%5.1f|%5.1f] " +
						"wdth [%5.1f] " +
						"hgth [%5.1f]",
						bounds.getMinX(), bounds.getMinY(),
						bounds.getMaxX(), bounds.getMaxY(),
						bounds.getWidth(),
						bounds.getHeight()
				);
	}

	/**
	 * orthogonal orientation
	 */
	public enum Orientation
	{
		LEFT,
		RIGHT,
		UP,
		DOWN;
		
		public boolean isHorizontal()
		{
			return (this == LEFT) || (this == RIGHT);
		}
		
		public boolean isVertical()
		{
			return !isHorizontal();
		}
	}
}